package org.browntooth.app.bluetooth;

import java.io.IOException;
import java.util.Set;
import java.util.UUID;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;

public class Bluetooth {
	private final Activity activity;
	private final BluetoothAdapter bluetoothAdapter;

	private final BluetoothBroadcastReceiver broadcastReceiver;

	private BluetoothEventListener listener;

	public Bluetooth(Activity activity) throws BluetoothNotSupportedException {
		this.activity = activity;

		bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

		if (bluetoothAdapter == null) {
			throw new BluetoothNotSupportedException();
		}

		broadcastReceiver = new BluetoothBroadcastReceiver();
		IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
		this.activity.registerReceiver(broadcastReceiver, filter);
	}

	public void setListener(BluetoothEventListener listener) {
		this.listener = listener;
	}

	public void tryToEnsureBluetooth() {
		// TODO
	}

	public void tryToEnsureDiscoverability() {
		// TODO
	}

	public void startDiscovery() {
		bluetoothAdapter.startDiscovery();
	}

	public void cancelDiscovery() {
		bluetoothAdapter.cancelDiscovery();
	}

	public boolean isDiscovering() {
		return bluetoothAdapter.isDiscovering();
	}

	public void validateEnabled() throws BluetoothDisabledException {
		if (!bluetoothAdapter.isEnabled()) {
			throw new BluetoothDisabledException();
		}
	}

	private void deviceDiscovered(BluetoothDevice device) {
		if (listener != null) {
			listener.deviceDiscovered(device);
		}
		// Log.d("Browntooth", "DEVICE: " + device.getAddress() + ":"
		// + device.getName());
	}

	public Set<BluetoothDevice> getPairedDevices() {
		return bluetoothAdapter.getBondedDevices();
	}

	public BluetoothDevice findPairedDevice(String address) {
		Set<BluetoothDevice> devices = getPairedDevices();

		for (BluetoothDevice device : devices) {
			if (device.getAddress().equals(address)) {
				return device;
			}
		}

		return null;
	}

	public void listen(UUID serviceUuid, String name) {
		ServerThread serverThread = new ServerThread(serviceUuid, name);
		serverThread.start();
	}

	private void remoteDeviceConnected(UUID serviceUuid, BluetoothSocket socket) {
		if (listener != null) {
			BluetoothDeviceConnection connection = new BluetoothDeviceConnection(null, serviceUuid, socket);

			listener.connectedToDivece(connection);
		}
	}

	private void remoteDeviceConnectionFailed(UUID serviceUuid, Exception exception) {
		if (listener != null) {
			listener.remoteDeviceConnectionFailed(serviceUuid, exception);
		}
	}

	public void connect(BluetoothDevice device, UUID serviceUuid) throws IOException {
		// TODO: Validate is not connecting.

		ConnectThread connectThread = new ConnectThread(device, serviceUuid);
		connectThread.start();
	}

	private void connectedToDivece(BluetoothDevice device, UUID serviceUuid, BluetoothSocket socket) {
		if (listener != null) {
			BluetoothDeviceConnection connection = new BluetoothDeviceConnection(device, serviceUuid, socket);

			listener.connectedToDivece(connection);
		}
	}

	private void connectionToDiveceFailed(BluetoothDevice device, UUID serviceUuid, Exception exception) {
		if (listener != null) {
			listener.connectionToDiveceFailed(device, serviceUuid, exception);
		}
	}

	private class BluetoothBroadcastReceiver extends BroadcastReceiver {
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();

			if (BluetoothDevice.ACTION_FOUND.equals(action)) {
				BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

				deviceDiscovered(device);
			}
		}
	}

	private class ConnectThread extends Thread {
		private final BluetoothDevice device;
		private final UUID serviceUuid;
		private final BluetoothSocket socket;

		public ConnectThread(BluetoothDevice device, UUID serviceUuid) throws IOException {
			this.device = device;
			this.serviceUuid = serviceUuid;

			BluetoothSocket socketTmp = null;
			try {
				socketTmp = device.createRfcommSocketToServiceRecord(serviceUuid);
			} catch (Exception e) {
				connectionToDiveceFailed(device, serviceUuid, e);
			}

			socket = socketTmp;
		}

		private boolean isFailed() {
			return socket == null;
		}

		@Override
		public synchronized void start() {
			if (!isFailed()) {
				super.start();
			}
		}

		@Override
		public void run() {
			if (socket == null) {
				return;
			}

			try {
				socket.connect();
			} catch (Exception e) {
				connectionToDiveceFailed(device, serviceUuid, e);
			}

			connectedToDivece(device, serviceUuid, socket);
		}
	}

	private class ServerThread extends Thread {
		private final UUID serviceUuid;

		private final BluetoothServerSocket serverSocket;

		public ServerThread(UUID serviceUuid, String name) {
			this.serviceUuid = serviceUuid;

			BluetoothServerSocket serverSocketTmp = null;
			try {
				serverSocketTmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(name, serviceUuid);
			} catch (Exception e) {
				remoteDeviceConnectionFailed(serviceUuid, e);
			}

			serverSocket = serverSocketTmp;
		}

		@Override
		public synchronized void start() {
			if (serverSocket != null) {
				super.start();
			}
		}

		@Override
		public void run() {
			if (serverSocket == null) {
				return;
			}

			BluetoothSocket socket = null;

			boolean wasError = false;
			Exception exception = null;
			while (true) {
				try {
					Log.d("Browntooth", "1");
					socket = serverSocket.accept();
					Log.d("Browntooth", "2");
				} catch (Exception e) {
					wasError = true;
					exception = e;
				}

				if (!wasError) {
					remoteDeviceConnected(serviceUuid, socket);
				} else {
					remoteDeviceConnectionFailed(serviceUuid, exception);
				}
			}

			// try {
			// serverSocket.close();
			// } catch (Exception e) {
			// wasError = true;
			// exception = e;
			// }
		}
	}
}
